#pragma once
#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include "Tools.h"
#include "Paraser.h"
#include "Epoller.h"
#include "Paraser.h"
#include "Command.h"
using namespace std;

#define DEFAULT_LISTENING_PORT 6379


enum RedisType{
    REDIS_UNINIT,
    REDIS_MASTER,
    REDIS_SLAVE,
};

class Redis{
public:
    bool run_flag=true;
    RedisType redis_type=REDIS_UNINIT;
    unordered_map<string, pair<string,chrono::time_point<std::chrono::system_clock>>> mp;
    uint16_t port=DEFAULT_LISTENING_PORT;
    string ip="127.0.0.1";

    ~Redis() {
        close(listen_fd_);
    }
    virtual void init()=0;
    void run(){
        while (run_flag) {
            int event_count = epoll_.Wait();
            for (int i = 0; i < event_count; ++i) {
                int fd = epoll_.GetEventFd(i);
                uint32_t events = epoll_.GetEvents(i);

                if (fd == listen_fd_) {
                    AcceptConnection();
                }else if (events &  (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                    CloseConnection(fd);
                }
                else if (events & EPOLLIN) {
                    HandleRequest(fd);
                } 
            }
        }
    };
protected:
    void init_socket();

    void CloseConnection(int fd) {
        //cout << "Client[" << fd << "] disconnected!" << endl;
        close(fd);
        epoll_.DelFd(fd);
    }
    void AcceptConnection() {
        struct sockaddr_in cli_addr;
        socklen_t cli_addr_len = sizeof(cli_addr);
        int conn_fd = accept(listen_fd_, (struct sockaddr*)&cli_addr, &cli_addr_len);
        if (conn_fd == -1) {
            perror("accept failed");
            return;
        }

        //std::cout << "Accepted new connection" << std::endl;
        epoll_.AddFd(conn_fd, EPOLLIN );
    }

    virtual void HandleRequest(int fd)=0;
    Epoller epoll_;
    int listen_fd_; 
    char buffer_[1024];
};

class RedisMaster : public Redis {
public:

    string master_replid="8371b4fb1155b71f4a04d3e1bc3e18c4a990aeeb";
    int master_repl_offset=0;
    void init();
    void add_slave(int fd){
        slaves_fd.push_back(fd);
    }
    
private:
    void HandleRequest(int fd){
        ssize_t bytes_read = recv(fd, buffer_, sizeof(buffer_), 0);
        if (bytes_read == -1) {
            perror("recv failed");
            return;
        }
        if(bytes_read > 0){
            buffer_[bytes_read-1] = '\0';
            //cout<<"receive "<<buffer_ << "size "<<bytes_read <<endl;
            unique_ptr<Command> command_ptr=Paraser::parse_message(buffer_,bytes_read);
            string back_msg=command_ptr->excute_command();
            #ifdef DEBUG
                command_ptr->show_info();
            #endif
            ssize_t bytes_written = send(fd, back_msg.c_str(), back_msg.size(), 0);
            if (bytes_written == -1) {
                perror("send failed");
                return;
            }
            if(command_ptr->command_type==CommandType::SET){
                for(auto slave_fd:slaves_fd){
                    send(slave_fd, buffer_, bytes_read, 0);
                }
            }
        }

    }
    
    vector<int> slaves_fd;

};

class RedisSlave : public Redis {
public:
    string master_ip;
    int master_port=0;

    void init();
    void HandleRequest(int fd){
        ssize_t bytes_read = recv(fd, buffer_, sizeof(buffer_), 0);
        if (bytes_read == -1) {
            perror("recv failed");
            return;
        }
        if(bytes_read == 0){
            epoll_.DelFd(fd);
        }else if(bytes_read > 0){
            buffer_[bytes_read-1] = '\0';
            //cout<<"receive "<<buffer_ << "size "<<bytes_read <<endl;
            unique_ptr<Command> command_ptr=Paraser::parse_message(buffer_,bytes_read);
            assert(command_ptr->command_type==CommandType::SET);
            string back_msg=command_ptr->excute_command();
        }
    };
};